<!-- build time:Tue Feb 18 2020 17:19:27 GMT+0800 (China Standard Time) --><!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"><meta name="renderer" content="webkit"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="format-detection" content="telephone=no,email=no,adress=no"><meta name="theme-color" content="#000000"><meta http-equiv="window-target" content="_top"><title>Spring Boot 数据库版本管理入门 | calm&#39;s blog</title><meta name="description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;database-version-control&#x2F; 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述在我们的认知中，我们会使用 SVN 或 Git 进行代码的版本管理。但是，我们是否好奇过，数据库也是需要进行版本管理的呢？PRED 环境，一般连接 PROD 数据库，做准生产的验收。那么，"><meta property="og:type" content="article"><meta property="og:title" content="Spring Boot 数据库版本管理入门"><meta property="og:url" content="https://calm_java.gitee.io/blog/2020/02/17/labs-springboot2-database-version-control/index.html"><meta property="og:site_name" content="卡姆"><meta property="og:description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;database-version-control&#x2F; 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述在我们的认知中，我们会使用 SVN 或 Git 进行代码的版本管理。但是，我们是否好奇过，数据库也是需要进行版本管理的呢？PRED 环境，一般连接 PROD 数据库，做准生产的验收。那么，"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/17/labs-springboot2-database-version-control/1.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/17/labs-springboot2-database-version-control/2.png"><meta property="article:published_time" content="2020-02-17T12:05:54.000Z"><meta property="article:modified_time" content="2020-02-17T14:21:15.643Z"><meta property="article:author" content="calm"><meta property="article:tag" content="数据库版本管理"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://calm_java.gitee.io/blog/2020/02/17/labs-springboot2-database-version-control/1.png"><link rel="canonical" href="https://calm_java.gitee.io/blog/2020/02/17/labs-springboot2-database-version-control/index.html"><link rel="alternate" href="/atom.xml" title="卡姆" type="application/atom+xml"><link rel="icon" href="/blog/images/avatar.jpg" type="image/x-icon"><link rel="stylesheet" href="/blog/css/style.css"><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.css"><meta name="generator" content="Hexo 4.2.0"></head><body class="main-center theme-green" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="slimContent"><div class="navbar-header"><div class="profile-block text-center"><a id="avatar" href="https://github.com/yuyong725" target="_blank"><img class="img-circle img-rotate" src="/blog/images/avatar.jpg" width="200" height="200"></a><h2 id="name" class="hidden-xs hidden-sm">卡姆</h2><h3 id="title" class="hidden-xs hidden-sm hidden-md">a Java coder need calm</h3><small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> hangzhou, China</small></div><div class="search" id="search-form-wrap"><form class="search-form sidebar-form"><div class="input-group"><input type="text" class="search-form-input form-control" placeholder="搜索"> <span class="input-group-btn"><button type="submit" class="search-form-submit btn btn-flat" onclick="return!1"><i class="icon icon-search"></i></button></span></div></form><div class="ins-search"><div class="ins-search-mask"></div><div class="ins-search-container"><div class="ins-input-wrapper"><input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech> <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="ins-section-wrapper"><div class="ins-section-container"></div></div></div></div></div><button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false"><span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span></button></div><nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation"><ul class="nav navbar-nav main-nav"><li class="menu-item menu-item-home"><a href="/blog/."><i class="icon icon-home-fill"></i> <span class="menu-title">首页</span></a></li><li class="menu-item menu-item-archives"><a href="/blog/archives"><i class="icon icon-archives-fill"></i> <span class="menu-title">归档</span></a></li><li class="menu-item menu-item-categories"><a href="/blog/categories"><i class="icon icon-folder"></i> <span class="menu-title">分类</span></a></li><li class="menu-item menu-item-tags"><a href="/blog/tags"><i class="icon icon-tags"></i> <span class="menu-title">标签</span></a></li><li class="menu-item menu-item-repository"><a href="/blog/repository"><i class="icon icon-project"></i> <span class="menu-title">项目</span></a></li><li class="menu-item menu-item-books"><a href="/blog/books"><i class="icon icon-book-fill"></i> <span class="menu-title">书单</span></a></li><li class="menu-item menu-item-links"><a href="/blog/links"><i class="icon icon-friendship"></i> <span class="menu-title">友链</span></a></li><li class="menu-item menu-item-about"><a href="/blog/about"><i class="icon icon-cup-fill"></i> <span class="menu-title">关于</span></a></li></ul><ul class="social-links"><li><a href="https://github.com/yuyong725" target="_blank" title="Github" data-toggle="tooltip" data-placement="top"><i class="icon icon-github"></i></a></li></ul></nav></div></header><aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><div class="widget"><h3 class="widget-title">公告</h3><div class="widget-body"><div id="board"><div class="content"><p>欢迎交流与分享经验!</p></div></div></div></div><div class="widget"><h3 class="widget-title">分类</h3><div class="widget-body"><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/blog/categories/CI-DI/">CI/DI</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/git/">git</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/labs/">labs</a><span class="category-list-count">14</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签</h3><div class="widget-body"><ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/MyBatis/" rel="tag">MyBatis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Oauth2/" rel="tag">Oauth2</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Spring-Security/" rel="tag">Spring Security</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/SpringMVC/" rel="tag">SpringMVC</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/docker/" rel="tag">docker</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/dubbo/" rel="tag">dubbo</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/git/" rel="tag">git</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jdbctemplate/" rel="tag">jdbctemplate</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jerkins/" rel="tag">jerkins</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/job/" rel="tag">job</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jpa/" rel="tag">jpa</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/pdf/" rel="tag">pdf</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" rel="tag">分库分表</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" rel="tag">多数据源</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" rel="tag">数据库版本管理</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" rel="tag">数据库连接池</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a><span class="tag-list-count">2</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签云</h3><div class="widget-body tagcloud"><a href="/blog/tags/MyBatis/" style="font-size:13px">MyBatis</a> <a href="/blog/tags/Oauth2/" style="font-size:13px">Oauth2</a> <a href="/blog/tags/Spring-Security/" style="font-size:13px">Spring Security</a> <a href="/blog/tags/SpringMVC/" style="font-size:13px">SpringMVC</a> <a href="/blog/tags/docker/" style="font-size:13px">docker</a> <a href="/blog/tags/dubbo/" style="font-size:13px">dubbo</a> <a href="/blog/tags/git/" style="font-size:13px">git</a> <a href="/blog/tags/jdbctemplate/" style="font-size:13px">jdbctemplate</a> <a href="/blog/tags/jerkins/" style="font-size:13px">jerkins</a> <a href="/blog/tags/job/" style="font-size:14px">job</a> <a href="/blog/tags/jpa/" style="font-size:13px">jpa</a> <a href="/blog/tags/pdf/" style="font-size:13px">pdf</a> <a href="/blog/tags/redis/" style="font-size:13px">redis</a> <a href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" style="font-size:13px">分库分表</a> <a href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" style="font-size:13px">多数据源</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" style="font-size:13px">数据库版本管理</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" style="font-size:13px">数据库连接池</a> <a href="/blog/tags/%E7%BC%93%E5%AD%98/" style="font-size:14px">缓存</a></div></div><div class="widget"><h3 class="widget-title">归档</h3><div class="widget-body"><ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/02/">二月 2020</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/01/">一月 2020</a><span class="archive-list-count">8</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2019/12/">十二月 2019</a><span class="archive-list-count">4</span></li></ul></div></div><div class="widget"><h3 class="widget-title">最新文章</h3><div class="widget-body"><ul class="recent-post-list list-unstyled no-thumbnail"><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-async-Job/" class="title">Spring Boot 异步任务入门</a></p><p class="item-date"><time datetime="2020-02-18T05:10:39.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-job/" class="title">Spring Boot 定时任务入门</a></p><p class="item-date"><time datetime="2020-02-18T02:26:31.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-database-version-control/" class="title">Spring Boot 数据库版本管理入门</a></p><p class="item-date"><time datetime="2020-02-17T12:05:54.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-sharding-datasource/" class="title">Spring Boot 分库分表入门</a></p><p class="item-date"><time datetime="2020-02-17T08:15:31.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/13/labs-springboot2-dynamic-datasource/" class="title">Spring Boot 多数据源（读写分离）入门</a></p><p class="item-date"><time datetime="2020-02-13T03:18:37.000Z" itemprop="datePublished">2020-02-13</time></p></div></li></ul></div></div></div></aside><aside class="sidebar sidebar-toc collapse in" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><nav id="toc" class="article-toc"><h3 class="toc-title">文章目录</h3><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1-概述"><span class="toc-text">1. 概述</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2-Flyway"><span class="toc-text">2. Flyway</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#2-1-引入依赖"><span class="toc-text">2.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-2-应用配置文件"><span class="toc-text">2.2 应用配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-3-FlywayApplication"><span class="toc-text">2.3 FlywayApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-4-SQL-based-migrations"><span class="toc-text">2.4 SQL-based migrations</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-5-Java-based-migrations"><span class="toc-text">2.5 Java-based migrations</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-6-其它功能"><span class="toc-text">2.6 其它功能</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3-Liquibase"><span class="toc-text">3. Liquibase</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#3-1-引入依赖"><span class="toc-text">3.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-2-应用配置文件"><span class="toc-text">3.2 应用配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-3-变更文件-Change-Log"><span class="toc-text">3.3 变更文件 Change Log</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-4-LiqiubaseApplication"><span class="toc-text">3.4 LiqiubaseApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-5-Property-based-migrations"><span class="toc-text">3.5 Property-based migrations</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-6-SQL-based-migrations"><span class="toc-text">3.6 SQL-based migrations</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-7-Java-based-migrations"><span class="toc-text">3.7 Java-based migrations</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-8-其它功能"><span class="toc-text">3.8 其它功能</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#666-彩蛋"><span class="toc-text">666. 彩蛋</span></a></li></ol></nav></div></aside><main class="main" role="main"><div class="content"><article id="post-labs-springboot2-database-version-control" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting"><div class="article-header"><h1 class="article-title" itemprop="name">Spring Boot 数据库版本管理入门</h1><div class="article-meta"><span class="article-date"><i class="icon icon-calendar-check"></i> <a href="/blog/2020/02/17/labs-springboot2-database-version-control/" class="article-date"><time datetime="2020-02-17T12:05:54.000Z" itemprop="datePublished">2020-02-17</time></a></span> <span class="article-category"><i class="icon icon-folder"></i> <a class="article-category-link" href="/blog/categories/labs/">labs</a></span> <span class="article-tag"><i class="icon icon-tags"></i> <a class="article-tag-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" rel="tag">数据库版本管理</a></span> <span class="article-read hidden-xs"><i class="icon icon-eye-fill" aria-hidden="true"></i> <span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv">0</span></span></span> <span class="post-comment"><i class="icon icon-comment"></i> <a href="/blog/2020/02/17/labs-springboot2-database-version-control/#comments" class="article-comment-link">评论</a></span> <span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计: 9.7k(字)</span> <span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长: 47(分)</span></div></div><div class="article-entry marked-body" itemprop="articleBody"><blockquote><p>摘要: 原创出处 <a href="http://www.iocoder.cn/Spring-Boot/database-version-control/" target="_blank" rel="noopener">http://www.iocoder.cn/Spring-Boot/database-version-control/</a> 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！</p></blockquote><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>在我们的认知中，我们会使用 SVN 或 Git 进行代码的版本管理。但是，我们是否好奇过，数据库也是需要进行版本管理的呢？</p><blockquote><p>PRED 环境，一般连接 PROD 数据库，做准生产的验收。</p></blockquote><p>那么，就意味着我们需要对 DEV、UAT、PROD 数据库都做一遍操作。“人，是系统最大的 BUG”。很多时候，我们并不能保证像机器一样，每次都操作都正确，这就导致在不同的环境下，因为数据的每个版本的初始化，带来额外的验收成本。</p><p>甚至说，因为我们常常是手动操作 DEV 数据库，没有整理一个<strong>完整</strong>清单，保证我们在 UAT、PROD 数据库中执行相同的操作。</p><p>基于以上种种，如果我们能像管理代码版本一样，来管理我们的数据库版本，是不是这些问题可以得到很好的解决？答案是，绝大多数是的。</p><p>目前，技术社区已经提供了很多解决方案。例如说：</p><ul><li><a href="https://flywaydb.org/" target="_blank" rel="noopener">Flyway</a></li><li><a href="https://www.liquibase.org/" target="_blank" rel="noopener">Liquibase</a></li><li><a href="http://dbdeploy.com/" target="_blank" rel="noopener">dbdeploy</a></li><li><a href="https://www.red-gate.com/products/sql-development/sql-source-control/" target="_blank" rel="noopener">SQL Source Control</a></li><li><a href="https://dbv.vizuina.com/" target="_blank" rel="noopener">dbv</a></li><li><a href="http://mybatis.org/migrations/" target="_blank" rel="noopener">MyBatis Migrations</a></li></ul><p>在 Spring Boot 项目中，提供了对 Flyway 和 Liquibase 的<strong>内置</strong>支持，所以在有数据库版本的需求时，肯定是推荐它们两。</p><p>本文，我们会对 Flyway 和 Liquibase 进行入门学习。这样，我们在学习它们的同时，可以有比较直观的使用感受，方便后续我们对它们进行选型。</p><h1 id="2-Flyway"><a href="#2-Flyway" class="headerlink" title="2. Flyway"></a>2. Flyway</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-data-access/labs-springboot2-database-version-control/labs-springboot2-flyway" target="_blank" rel="noopener">labs-springboot2-flyway</a> 。</p></blockquote><blockquote><p>Version control for your database.</p><p>数据库的版本管理。</p></blockquote><p>Flyway 支持的数据库，主要是关系数据库。如下图所示：<img src="1.png" alt="支持的数据库"></p><p>Flyway 提供了 <a href="https://flywaydb.org/documentation/migrations#sql-based-migrations" target="_blank" rel="noopener">SQL-based migrations</a> 和 <a href="https://flywaydb.org/documentation/migrations#sql-based-migrations" target="_blank" rel="noopener">Java-based migrations</a> 两种数据库变更方式。</p><ul><li>前者使用简单，无需编写 Java 代码。</li><li>后者需要使用 Java 编写代码，胜在灵活。</li></ul><p>一般情况下，如果是做表的变更，或者记录的简单插入、更新、删除等操作，使用 SQL-based migrations 即可。</p><p>复杂场景下，我们可能需要关联多个表，则需要通过编写 Java 代码，进行逻辑处理，此时就是和使用 Java-based migrations 了。</p><p>下面，让我们来使用它们二者，更好的体会它们的区别。</p><h2 id="2-1-引入依赖"><a href="#2-1-引入依赖" class="headerlink" title="2.1 引入依赖"></a>2.1 引入依赖</h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-database-version-control<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot-flyway<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对数据库连接池的自动化配置 --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 同时，spring-boot-starter-jdbc 支持 Flyway 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-jdbc<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 本示例，我们使用 MySQL --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- Flyway 依赖 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.flywaydb<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>flyway-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具体每个依赖的作用，胖友自己认真看下添加的所有注释噢。</p><h2 id="2-2-应用配置文件"><a href="#2-2-应用配置文件" class="headerlink" title="2.2 应用配置文件"></a>2.2 应用配置文件</h2><p>在 <code>resources</code> 目录下，创建 <code>application.yaml</code> 配置文件。配置如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="comment"># datasource 数据源配置内容，对应 DataSourceProperties 配置属性类</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/flyway_learn?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span> <span class="comment"># 数据库账号</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">root</span> <span class="comment"># 数据库密码</span></span><br><span class="line">  <span class="comment"># flyway 配置内容，对应 FlywayAutoConfiguration.FlywayConfiguration 配置项</span></span><br><span class="line">  <span class="attr">flyway:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span> <span class="comment"># 开启 Flyway 功能</span></span><br><span class="line">    <span class="attr">cleanDisabled:</span> <span class="literal">true</span> <span class="comment"># 禁用 Flyway 所有的 drop 相关的逻辑，避免出现跑路的情况。</span></span><br><span class="line">    <span class="attr">locations:</span> <span class="comment"># 迁移脚本目录</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">classpath:db/migration</span> <span class="comment"># 配置 SQL-based 的 SQL 脚本在该目录下</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">classpath:cn.javadog.labs.springboot2.flyway.migration</span> <span class="comment"># 配置 Java-based 的 Java 文件在该目录下</span></span><br><span class="line">    <span class="attr">check-location:</span> <span class="literal">false</span> <span class="comment"># 是否校验迁移脚本目录下。如果配置为 true ，代表需要校验。此时，如果目录下没有迁移脚本，会抛出 IllegalStateException 异常</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/flyway_learn?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai</span> <span class="comment"># 数据库地址</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">root</span> <span class="comment"># 数据库账号</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">root</span> <span class="comment"># 数据库密码</span></span><br></pre></td></tr></table></figure><ul><li><code>spring.datasource</code> 配置项，设置数据源的配置。这里暂时没有实际作用，仅仅是为了项目不报数据源的错误。</li><li><code>spring.flyway</code> 配置项，设置 Flyway 的属性，而后可以被 <code>FlywayAutoConfiguration</code> 自动化配置。<ul><li>每个配置项的作用，胖友自己看下注释。更多的配置项，可以看看 <a href="https://my.oschina.net/cncodemaker/blog/829963" target="_blank" rel="noopener">《Spring Boot 配置属性详解 – Migration》</a> 文章。</li><li>重点看下 <code>locations</code> 配置项，我们分别设置了 SQL 和 Java 迁移脚本的所在目录。</li></ul></li></ul><h2 id="2-3-FlywayApplication"><a href="#2-3-FlywayApplication" class="headerlink" title="2.3 FlywayApplication"></a>2.3 FlywayApplication</h2><p>创建 <code>FlywayApplication.java</code> 类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FlywayApplication.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FlywayApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 启动 Spring Boot 应用</span></span><br><span class="line">        SpringApplication.run(FlywayApplication<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动项目。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Flyway 的信息</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.060</span>  INFO <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.internal</span><span class="variable">.license</span><span class="variable">.VersionPrinter</span>    : Flyway Community Edition <span class="number">5</span><span class="variable">.2</span><span class="variable">.4</span> by Boxfuse</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.237</span>  INFO <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.internal</span><span class="variable">.database</span><span class="variable">.DatabaseFactory</span>  : Database: jdbc:mysql:<span class="comment">//127.0.0.1:3306/flyway_learn (MySQL 8.0)</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.289</span>  WARN <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.i</span><span class="variable">.s</span><span class="variable">.classpath</span><span class="variable">.ClassPathScanner</span>     : Unable to resolve location classpath:cn/javadog/labs/springboot2/flyway/migration</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.304</span>  WARN <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.i</span><span class="variable">.s</span><span class="variable">.classpath</span><span class="variable">.ClassPathScanner</span>     : Unable to resolve location classpath:db/migration</span><br><span class="line"></span><br><span class="line"><span class="comment">// 发现 0 个迁移脚本。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.329</span>  INFO <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbValidate</span>     : Successfully validated <span class="number">0</span> migrations (execution <span class="keyword">time</span> <span class="number">00</span>:<span class="number">00</span><span class="variable">.007s</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 flyway_schema_history 表</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.354</span>  INFO <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.i</span><span class="variable">.s</span><span class="variable">.JdbcTableSchemaHistory</span>         : Creating Schema History <span class="keyword">table</span>: <span class="meta">`flyway_learn`.`flyway_schema_history`</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 打印当前数据库的迁移版本</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.400</span>  INFO <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbMigrate</span>      : Current version of schema <span class="meta">`flyway_learn`: &lt;&lt; Empty Schema &gt;&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断，没有需要迁移的脚本</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.400</span>  INFO <span class="number">12089</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbMigrate</span>      : Schema <span class="meta">`flyway_learn` is up to date. No migration necessary.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动项目完成</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">19</span>:<span class="number">33</span><span class="variable">.457</span>  INFO <span class="number">12089</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.flyway</span><span class="variable">.FlywayApplication</span>         : Started FlywayApplication in <span class="number">1</span><span class="variable">.212</span> seconds (JVM running <span class="keyword">for</span> <span class="number">1</span><span class="variable">.773</span>)</span><br></pre></td></tr></table></figure><ul><li>看下日志内容上的注释。</li></ul><p>在启动的日志中，我们看到 Flyway 会自动创建 <code>flyway_schema_history</code> 表，记录 Flyway 每次迁移( migration )的历史。表结构如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`flyway_schema_history`</span> (</span><br><span class="line">  <span class="string">`installed_rank`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 安装顺序，从 1 开始递增。</span></span><br><span class="line">  <span class="string">`version`</span> <span class="built_in">varchar</span>(<span class="number">50</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- 版本号</span></span><br><span class="line">  <span class="string">`description`</span> <span class="built_in">varchar</span>(<span class="number">200</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 迁移脚本描述</span></span><br><span class="line">  <span class="string">`type`</span> <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 脚本类型，目前有 SQL 和 Java 。</span></span><br><span class="line">  <span class="string">`script`</span> <span class="built_in">varchar</span>(<span class="number">1000</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 脚本地址</span></span><br><span class="line">  <span class="string">`checksum`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- 脚本校验码。避免已经执行的脚本，被人变更了。</span></span><br><span class="line">  <span class="string">`installed_by`</span> <span class="built_in">varchar</span>(<span class="number">100</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 执行脚本的数据库用户</span></span><br><span class="line">  <span class="string">`installed_on`</span> <span class="built_in">timestamp</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">CURRENT_TIMESTAMP</span>, <span class="comment">-- 安装时间</span></span><br><span class="line">  <span class="string">`execution_time`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 执行时长，单位毫秒</span></span><br><span class="line">  <span class="string">`success`</span> <span class="built_in">tinyint</span>(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 执行结果是否成功。1-成功。0-失败</span></span><br><span class="line">  PRIMARY <span class="keyword">KEY</span> (<span class="string">`installed_rank`</span>),</span><br><span class="line">  <span class="keyword">KEY</span> <span class="string">`flyway_schema_history_s_idx`</span> (<span class="string">`success`</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8mb4 <span class="keyword">COLLATE</span>=utf8mb4_bin;</span><br></pre></td></tr></table></figure><ul><li>大体看下每个字段的注释，后面对着具体的记录，会更容易理解。</li></ul><h2 id="2-4-SQL-based-migrations"><a href="#2-4-SQL-based-migrations" class="headerlink" title="2.4 SQL-based migrations"></a>2.4 SQL-based migrations</h2><p>在 <code>resources/db/migration</code> 目录下，创建 <code>V1.0__INIT_DB.sql</code> SQL 迁移脚本。如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建用户表</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`flyway_users`</span> (</span><br><span class="line">  <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT <span class="keyword">COMMENT</span> <span class="string">'用户编号'</span>,</span><br><span class="line">  <span class="string">`username`</span> <span class="built_in">varchar</span>(<span class="number">64</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'账号'</span>,</span><br><span class="line">  <span class="string">`password`</span> <span class="built_in">varchar</span>(<span class="number">32</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'密码'</span>,</span><br><span class="line">  <span class="string">`create_time`</span> datetime <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'创建时间'</span>,</span><br><span class="line">  PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>),</span><br><span class="line">  <span class="keyword">UNIQUE</span> <span class="keyword">KEY</span> <span class="string">`idx_username`</span> (<span class="string">`username`</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8mb4 <span class="keyword">COLLATE</span>=utf8mb4_bin;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入一条数据</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="string">`flyway_users`</span>(username, <span class="keyword">password</span>, create_time) <span class="keyword">VALUES</span>(<span class="string">'calm flyway'</span>, <span class="string">'password'</span>, <span class="keyword">now</span>());</span><br></pre></td></tr></table></figure><ul><li>比较简单，就是创建用户表 <code>flyway_users</code> 表，并往里面插入一条记录。</li></ul><blockquote><p>FROM <a href="https://flywaydb.org/documentation/migrations#naming-1" target="_blank" rel="noopener">https://flywaydb.org/documentation/migrations#naming-1</a></p><p><img src="2.png" alt="Naming"></p></blockquote><ul><li><blockquote><p>在我们的示例中，我们使用 <code>V</code> 前缀，表示版本迁移。绝大多数情况下，我们只会使用 <code>V</code> 前缀。</p></blockquote></li><li><blockquote><p>在我们的示例中，我们使用 <code>1.0</code> 。</p></blockquote></li><li><p>Separator 分隔符：两个 <code>_</code> ，即 <code>__</code> 。可配置，不过一般不配置。</p></li><li><blockquote><p>在我们的示例中，我们使用 <code>INIT_DB</code> 。</p></blockquote></li><li><p>Suffix 后缀：<code>.sql</code> 。可配置，不过一般不配置。</p></li></ul><p>我们再次启动 Application 项目。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Flyway 的信息</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.267</span>  INFO <span class="number">12114</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.internal</span><span class="variable">.license</span><span class="variable">.VersionPrinter</span>    : Flyway Community Edition <span class="number">5</span><span class="variable">.2</span><span class="variable">.4</span> by Boxfuse</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.517</span>  INFO <span class="number">12114</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.internal</span><span class="variable">.database</span><span class="variable">.DatabaseFactory</span>  : Database: jdbc:mysql:<span class="comment">//127.0.0.1:3306/flyway_learn (MySQL 8.0)</span></span><br><span class="line"><span class="comment">// 发现一个迁移脚本，就是 V1.0__INIT_DB.sql 。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.570</span>  WARN <span class="number">12114</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.i</span><span class="variable">.s</span><span class="variable">.classpath</span><span class="variable">.ClassPathScanner</span>     : Unable to resolve location classpath:cn/javadog/labs/springboot2/flyway/migration</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.609</span>  INFO <span class="number">12114</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbValidate</span>     : Successfully validated <span class="number">1</span> migration (execution <span class="keyword">time</span> <span class="number">00</span>:<span class="number">00</span><span class="variable">.018s</span>)</span><br><span class="line"><span class="comment">// 打印当前数据库的迁移版本</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.620</span>  INFO <span class="number">12114</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbMigrate</span>      : Current version of schema <span class="meta">`flyway_learn`: &lt;&lt; Empty Schema &gt;&gt;</span></span><br><span class="line"><span class="comment">// 开始迁移到版本 1.0</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.621</span>  INFO <span class="number">12114</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbMigrate</span>      : Migrating schema <span class="meta">`flyway_learn` to version 1.0 - INIT DB</span></span><br><span class="line"><span class="comment">// 成功执行一个迁移</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.667</span>  INFO <span class="number">12114</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbMigrate</span>      : Successfully applied <span class="number">1</span> migration to schema <span class="meta">`flyway_learn` (execution time 00:00.053s)</span></span><br><span class="line"><span class="comment">// 启动项目完成</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span><span class="variable">.748</span>  INFO <span class="number">12114</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.flyway</span><span class="variable">.FlywayApplication</span>         : Started FlywayApplication in <span class="number">1</span><span class="variable">.392</span> seconds (JVM running <span class="keyword">for</span> <span class="number">2</span><span class="variable">.04</span>)</span><br></pre></td></tr></table></figure><ul><li>看下日志内容上的注释。</li></ul><p>此时，我们去查询下 MySQL 。如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show tables;</span><br><span class="line">+------------------------+</span><br><span class="line">| Tables_in_flyway_learn |</span><br><span class="line">+------------------------+</span><br><span class="line">| flyway_schema_history  |</span><br><span class="line">| flyway_users           |</span><br><span class="line">+------------------------+</span><br><span class="line">2 rows in set (0.00 sec)</span><br><span class="line"># 如上，我们可以看到两个表。</span><br><span class="line"># 其中，&#96;flyway_users&#96; 表，就是我们需要在 &#96;V1.0__INIT_DB.sql&#96; 迁移脚本中，需要创建的。</span><br><span class="line"></span><br><span class="line">mysql&gt; SELECT * FROM flyway_users;</span><br><span class="line">+----+-------------+----------+---------------------+</span><br><span class="line">| id | username    | password | create_time         |</span><br><span class="line">+----+-------------+----------+---------------------+</span><br><span class="line">|  1 | calm flyway | password | 2020-02-17 21:25:22 |</span><br><span class="line">+----+-------------+----------+---------------------+</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"># &#96;users&#96; 表的该记录，就是我们希望插入的一条记录。</span><br><span class="line"></span><br><span class="line">mysql&gt; SELECT * FROM flyway_schema_history;</span><br><span class="line">+----------------+---------+-------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+</span><br><span class="line">| installed_rank | version | description | type | script            | checksum    | installed_by | installed_on        | execution_time | success |</span><br><span class="line">+----------------+---------+-------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+</span><br><span class="line">|              1 | 1.0     | INIT DB     | SQL  | V1.0__INIT_DB.sql | -1491919606 | root         | 2020-02-17 21:25:22 |             35 |       1 |</span><br><span class="line">+----------------+---------+-------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"># &#96;flyway_schema_history&#96; 表中，增加了一条版本号为 &#96;1.0&#96; 的，使用 &#96;V1.0__INIT_DB.sql&#96; 迁移脚本的日志。</span><br></pre></td></tr></table></figure><ul><li>看下每个操作，以及其注释。</li></ul><p>我们再再次启动 Application 项目。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Flyway 的信息</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">32</span>:<span class="number">04</span><span class="variable">.716</span>  INFO <span class="number">12132</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.internal</span><span class="variable">.license</span><span class="variable">.VersionPrinter</span>    : Flyway Community Edition <span class="number">5</span><span class="variable">.2</span><span class="variable">.4</span> by Boxfuse</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">32</span>:<span class="number">04</span><span class="variable">.894</span>  INFO <span class="number">12132</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.internal</span><span class="variable">.database</span><span class="variable">.DatabaseFactory</span>  : Database: jdbc:mysql:<span class="comment">//127.0.0.1:3306/flyway_learn (MySQL 8.0)</span></span><br><span class="line"><span class="comment">// 发现一个迁移脚本，就是 V1.0__INIT_DB.sql 。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">32</span>:<span class="number">04</span><span class="variable">.943</span>  WARN <span class="number">12132</span> --- [           main] o<span class="variable">.f</span><span class="variable">.c</span><span class="variable">.i</span><span class="variable">.s</span><span class="variable">.classpath</span><span class="variable">.ClassPathScanner</span>     : Unable to resolve location classpath:cn/javadog/labs/springboot2/flyway/migration</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">32</span>:<span class="number">04</span><span class="variable">.973</span>  INFO <span class="number">12132</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbValidate</span>     : Successfully validated <span class="number">1</span> migration (execution <span class="keyword">time</span> <span class="number">00</span>:<span class="number">00</span><span class="variable">.015s</span>)</span><br><span class="line"><span class="comment">// 打印当前数据库的迁移版本为 `1.0`</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">32</span>:<span class="number">04</span><span class="variable">.984</span>  INFO <span class="number">12132</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbMigrate</span>      : Current version of schema <span class="meta">`flyway_learn`: 1.0</span></span><br><span class="line"><span class="comment">// 判断已经到达最新版本，无需执行迁移</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">32</span>:<span class="number">04</span><span class="variable">.985</span>  INFO <span class="number">12132</span> --- [           main] o<span class="variable">.f</span><span class="variable">.core</span><span class="variable">.internal</span><span class="variable">.command</span><span class="variable">.DbMigrate</span>      : Schema <span class="meta">`flyway_learn` is up to date. No migration necessary.</span></span><br><span class="line"><span class="comment">// 启动项目完成</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">32</span>:<span class="number">05</span><span class="variable">.045</span>  INFO <span class="number">12132</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.flyway</span><span class="variable">.FlywayApplication</span>         : Started FlywayApplication in <span class="number">1</span><span class="variable">.144</span> seconds (JVM running <span class="keyword">for</span> <span class="number">1</span><span class="variable">.749</span>)</span><br></pre></td></tr></table></figure><p>下面，我们注释掉 <code>V1.0__INIT_DB.sql</code> 迁移脚本中的，<code>INSERT</code> 操作。我们再再再次启动 Application 项目。会报如下错误：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.beans.factory.BeanCreationException: Error creating bean with name &#39;flywayInitializer&#39; defined in class path resource [org&#x2F;springframework&#x2F;boot&#x2F;autoconfigure&#x2F;flyway&#x2F;FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.api.FlywayException: Validate failed: Migration checksum mismatch for migration version 1.0</span><br><span class="line">-&gt; Applied to database : -1491919606</span><br><span class="line">-&gt; Resolved locally    : 294901865</span><br></pre></td></tr></table></figure><ul><li>Flyway 会给每个迁移脚本，计算出一个 <code>checksum</code> 字段。这样，每次启动时，都会校验已经安装( installed )的迁移脚本，是否发生了改变。如果是，抛出异常。这样，保证不会因为脚本变更，导致出现问题。</li></ul><h2 id="2-5-Java-based-migrations"><a href="#2-5-Java-based-migrations" class="headerlink" title="2.5 Java-based migrations"></a>2.5 Java-based migrations</h2><p>在 <code>cn.javadog.labs.springboot2.flyway.migration</code> 包路径下，创建 <code>V1_1__FixUsername.java</code> 类，修复 <code>users</code> 的用户名。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// V1_1__FixUsername.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> 余勇</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2020-01-11 00:43</span></span><br><span class="line"><span class="comment"> * 1.1版本</span></span><br><span class="line"><span class="comment"> * note 即使GG，也会留下记录，因此该class也是不能改的</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">V1_1__FixUsername</span> <span class="keyword">extends</span> <span class="title">BaseJavaMigration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">migrate</span><span class="params">(Context context)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// 创建 JdbcTemplate ，方便 JDBC 操作</span></span><br><span class="line">        JdbcTemplate template = <span class="keyword">new</span> JdbcTemplate(context.getConfiguration().getDataSource());</span><br><span class="line">        <span class="comment">// 查询所有用户，如果用户名为 calm flyway ，则变更成 calm</span></span><br><span class="line">        template.query(<span class="string">"SELECT id, username, password, create_time FROM flyway_users"</span>, rs -&gt; &#123;</span><br><span class="line">            <span class="comment">// 遍历返回的结果</span></span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                String username = rs.getString(<span class="string">"username"</span>);</span><br><span class="line">                <span class="keyword">if</span> (<span class="string">"calm flyway"</span>.equals(username)) &#123;</span><br><span class="line">                    Integer id = rs.getInt(<span class="string">"id"</span>);</span><br><span class="line">                    template.update(<span class="string">"UPDATE flyway_users SET username = ? WHERE id = ?"</span>,</span><br><span class="line">                            <span class="string">"calm"</span>, id);</span><br><span class="line">                    logger.info(<span class="string">"[migrate][更新 user(&#123;&#125;) 的用户名(&#123;&#125; =&gt; &#123;&#125;)"</span>, id, username, <span class="string">"calm"</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">while</span> (rs.next());</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 默认返回，是 null 。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Integer <span class="title">getChecksum</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">11</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">canExecuteInTransaction</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 默认返回，也是 true</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MigrationVersion <span class="title">getVersion</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 默认按照约定的规则，从类名中解析获得。可以自定义</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">super</span>.getVersion();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>比较简单，胖友看下代码注释。这里仅仅是示例，实际迁移的逻辑，会更加复杂。</li><li>Java 迁移脚本，可以通过类名按照和 <a href="https://calm_java.gitee.io/blog/2020/02/13/labs-springboot2-database-version-control/">「2.4 SQL-based migrations」</a> 一样的命名约定，自动获得版本号。当然，也可以通过重写 <code>#getVersion()</code> 方法，自定义版本号。</li></ul><p>我们再再再再次启动 Application 项目。执行日志如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; Flyway 的信息</span><br><span class="line">2020-02-17 21:35:43.420  INFO 12152 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 5.2.4 by Boxfuse</span><br><span class="line">2020-02-17 21:35:43.547  INFO 12152 --- [           main] o.f.c.internal.database.DatabaseFactory  : Database: jdbc:mysql:&#x2F;&#x2F;127.0.0.1:3306&#x2F;flyway_learn (MySQL 8.0)</span><br><span class="line">&#x2F;&#x2F; 发现2个迁移脚本，就是 V1.0__INIT_DB.sql 和 V1_1__FixUsername.java</span><br><span class="line">2020-02-17 21:35:43.609  INFO 12152 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 2 migrations (execution time 00:00.017s)</span><br><span class="line">&#x2F;&#x2F; 打印当前数据库的迁移版本为 &#96;1.0&#96;</span><br><span class="line">2020-02-17 21:35:43.623  INFO 12152 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema &#96;flyway_learn&#96;: 1.0</span><br><span class="line">&#x2F;&#x2F; 开始迁移到版本 1.1</span><br><span class="line">2020-02-17 21:35:43.625  INFO 12152 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema &#96;flyway_learn&#96; to version 1.1 - FixUsername</span><br><span class="line">2020-02-17 21:35:43.651  INFO 12152 --- [           main] c.j.l.s.f.migration.V1_1__FixUsername    : [migrate][更新 user(1) 的用户名(calm flyway &#x3D;&gt; calm)</span><br><span class="line">&#x2F;&#x2F; 成功执行一个迁移</span><br><span class="line">2020-02-17 21:35:43.673  INFO 12152 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 1 migration to schema &#96;flyway_learn&#96; (execution time 00:00.059s)</span><br><span class="line">&#x2F;&#x2F; 启动项目完成</span><br><span class="line">2020-02-17 21:35:43.784  INFO 12152 --- [           main] c.j.l.s.flyway.FlywayApplication         : Started FlywayApplication in 1.202 seconds (JVM running for 1.785)</span><br></pre></td></tr></table></figure><p>此时，我们去查询下 MySQL 。如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SELECT * FROM flyway_users;</span><br><span class="line">+<span class="comment">----+----------+----------+---------------------+</span></span><br><span class="line">| id | username | password | create_time         |</span><br><span class="line">+<span class="comment">----+----------+----------+---------------------+</span></span><br><span class="line">|  1 | calm     | password | 2020-02-17 21:25:22 |</span><br><span class="line">+<span class="comment">----+----------+----------+---------------------+</span></span><br><span class="line">1 row in <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># `flyway_users` 表的该记录，用户名被修改为 calm 。</span></span><br><span class="line"></span><br><span class="line">mysql&gt; <span class="keyword">SELECT</span> * <span class="keyword">FROM</span> flyway_schema_history;</span><br><span class="line">+<span class="comment">----------------+---------+-------------+------+----------------------------------------------------------------+-------------+--------------+---------------------+----------------+---------+</span></span><br><span class="line">| installed_rank | version | description | type | script                                                         | <span class="keyword">checksum</span>    | installed_by | installed_on        | execution_time | <span class="keyword">success</span> |</span><br><span class="line">+<span class="comment">----------------+---------+-------------+------+----------------------------------------------------------------+-------------+--------------+---------------------+----------------+---------+</span></span><br><span class="line">|              <span class="number">1</span> | <span class="number">1.0</span>     | INIT DB     | <span class="keyword">SQL</span>  | V1<span class="number">.0</span>__INIT_DB.sql                                              | <span class="number">-1491919606</span> | root         | <span class="number">2020</span><span class="number">-02</span><span class="number">-17</span> <span class="number">21</span>:<span class="number">25</span>:<span class="number">22</span> |             <span class="number">35</span> |       <span class="number">1</span> |</span><br><span class="line">|              <span class="number">2</span> | <span class="number">1.1</span>     | FixUsername | JDBC | cn.javadog.labs.springboot2.flyway.migration.V1_1__FixUsername |          <span class="number">11</span> | root         | <span class="number">2020</span><span class="number">-02</span><span class="number">-17</span> <span class="number">21</span>:<span class="number">35</span>:<span class="number">43</span> |             <span class="number">27</span> |       <span class="number">1</span> |</span><br><span class="line">+<span class="comment">----------------+---------+-------------+------+----------------------------------------------------------------+-------------+--------------+---------------------+----------------+---------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># `flyway_schema_history` 表中，增加了一条版本号为 `1.1` 的，使用 `V1_1__FixUsername.sql` 迁移脚本的日志。</span></span><br></pre></td></tr></table></figure><h2 id="2-6-其它功能"><a href="#2-6-其它功能" class="headerlink" title="2.6 其它功能"></a>2.6 其它功能</h2><p>Flyway 支持 <a href="https://flywaydb.org/documentation/callbacks#sql-callbacks" target="_blank" rel="noopener">SQL Callbacks</a> 和 <a href="https://flywaydb.org/documentation/callbacks#java-callbacks" target="_blank" rel="noopener">Java Callbacks</a> 两种<a href="https://flywaydb.org/documentation/callbacks" target="_blank" rel="noopener">回调</a>方式，让我们在 Flyway 的执行过程中，可以实现自定义的拓展。</p><p>在上述的示例，我们是基于 <a href="https://flywaydb.org/documentation/plugins/springboot" target="_blank" rel="noopener">Spring Boot</a> 的使用方式。而 Flyway 还提供了如下方式：</p><ul><li><p><a href="https://flywaydb.org/documentation/api/" target="_blank" rel="noopener">API (Java / Android)</a> 直接使用 Java 原生的 API 。</p></li><li><p><a href="https://flywaydb.org/documentation/commandline/" target="_blank" rel="noopener">Command-line tool</a> 命令行工具</p></li><li><p><a href="https://flywaydb.org/documentation/maven/" target="_blank" rel="noopener">Maven</a> 插件</p></li><li><p><a href="https://flywaydb.org/documentation/gradle/" target="_blank" rel="noopener">Gralde</a> 插件</p></li><li><p><a href="https://flywaydb.org/documentation/plugins/" target="_blank" rel="noopener">Community Plugins and Integrations</a> ，社区提供了 Ant、Intellij IDEA、Jenkins 等插件。</p><p>Flyway 还有一些其它细节，建议抽时间，通读下 <a href="https://flywaydb.org/documentation/" target="_blank" rel="noopener">Documentation</a> 文档。</p></li></ul><h1 id="3-Liquibase"><a href="#3-Liquibase" class="headerlink" title="3. Liquibase"></a>3. Liquibase</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-data-access/labs-springboot2-database-version-control/labs-springboot-liquibase" target="_blank" rel="noopener">labs-springboot-liquibase</a> 。</p></blockquote><blockquote><p>Liquibase is the leading open source tool for database change and deployment management.</p><p>Liquibase 是用于数据库变更和部署管理的领先的开源工具。</p></blockquote><p>Liquibase 支持的数据库，主要是关系数据库。如下图表格：</p><table><thead><tr><th align="left">Database</th><th align="left">Type Name</th><th align="left">Notes</th></tr></thead><tbody><tr><td align="left"><a href="https://www.liquibase.org/mysql.html" target="_blank" rel="noopener">MySQL</a></td><td align="left">mysql</td><td align="left">No Issues</td></tr><tr><td align="left"><a href="https://www.liquibase.org/mariadb.html" target="_blank" rel="noopener">MariaDB</a></td><td align="left">mysql</td><td align="left">MariaDB is 100% compatible with MySQL per <a href="https://mariadb.com/kb/en/library/mariadb-vs-mysql-compatibility/" target="_blank" rel="noopener">MariaDB developers</a></td></tr><tr><td align="left"><a href="https://www.liquibase.org/postgresql.html" target="_blank" rel="noopener">PostgreSQL</a></td><td align="left">postgresql</td><td align="left">8.2+ is required to use the “drop all database objects” functionality.</td></tr><tr><td align="left"><a href="https://www.liquibase.org/oracle.html" target="_blank" rel="noopener">Oracle</a></td><td align="left">oracle</td><td align="left">11g driver is required when using the diff tool on databases running with AL32UTF8 or AL16UTF16</td></tr><tr><td align="left"><a href="https://www.liquibase.org/mssql.html" target="_blank" rel="noopener">SQL Server</a></td><td align="left">mssql</td><td align="left">No Issues</td></tr><tr><td align="left">Sybase_Enterprise</td><td align="left">sybase</td><td align="left">ASE 12.0+ required. “select into” database option needs to be set. Best driver is JTDS. Sybase does not support transactions for DDL so rollbacks will not work on failures. Foreign keys can not be dropped which can break the rollback or dropAll functionality.</td></tr><tr><td align="left">Sybase_Anywhere</td><td align="left">asany</td><td align="left"><strong>Since 1.9</strong></td></tr><tr><td align="left">DB2</td><td align="left">db2</td><td align="left">No Issues. Will auto-call REORG when necessary.</td></tr><tr><td align="left"><a href="https://www.liquibase.org/apache_derby.html" target="_blank" rel="noopener">Apache_Derby</a></td><td align="left">derby</td><td align="left">No Issues</td></tr><tr><td align="left">HSQL</td><td align="left">hsqldb</td><td align="left">No Issues</td></tr><tr><td align="left">H2</td><td align="left">h2</td><td align="left">No Issues</td></tr><tr><td align="left"><a href="https://www.liquibase.org/informix.html" target="_blank" rel="noopener">Informix</a></td><td align="left">informix</td><td align="left">No Issues</td></tr><tr><td align="left">Firebird</td><td align="left">firebird</td><td align="left">No Issues</td></tr><tr><td align="left"><a href="https://www.liquibase.org/sqlite.html" target="_blank" rel="noopener">SQLite</a></td><td align="left">sqlite</td><td align="left">No Issues</td></tr></tbody></table><p>Liquibase 通过在变更日志( <a href="https://www.liquibase.org/documentation/databasechangelog.html" target="_blank" rel="noopener">Change Log</a> )文件，配置每一个变更集( <a href="https://www.liquibase.org/documentation/changeset.html" target="_blank" rel="noopener">Change Set</a> )，实现数据库变更的管理。</p><p>Liquibase 提供了多种格式，如下：</p><ul><li><a href="https://www.liquibase.org/documentation/xml_format.html" target="_blank" rel="noopener">XML Format</a></li><li><a href="https://www.liquibase.org/documentation/yaml_format.html" target="_blank" rel="noopener">YAML Format</a></li><li><a href="https://www.liquibase.org/documentation/json_format.html" target="_blank" rel="noopener">JSON Format</a></li><li><a href="https://www.liquibase.org/documentation/sql_format.html" target="_blank" rel="noopener">SQL Format</a></li><li><a href="https://www.liquibase.org/documentation/other_formats.html" target="_blank" rel="noopener">Other Formats</a></li></ul><p>在 Spring Boot 中，默认配置使用 YAML Format 。所以我们在入门的示例中，也使用这种格式。</p><p>Liquibase 在变更集( Change Set )中，除了提供了和 Flyway 的 <strong>SQL-based migrations</strong> 和 <strong>Java-based migrations</strong> 方式之外，额外提供了基于配置，自动生成对应的 SQL 操作。我们姑且称它为 “<strong>Property-based migrations</strong>” 吧。</p><p>下面，就让我们开始入门 Liquibase 吧。</p><h2 id="3-1-引入依赖"><a href="#3-1-引入依赖" class="headerlink" title="3.1 引入依赖"></a>3.1 引入依赖</h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-database-version-control<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot-liquibase<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对数据库连接池的自动化配置 --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 同时，spring-boot-starter-jdbc 支持 Liquibase 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-jdbc<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 本示例，我们使用 MySQL --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- Liquibase 依赖 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.liquibase<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>liquibase-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具体每个依赖的作用，胖友自己认真看下添加的所有注释噢。</p><h2 id="3-2-应用配置文件"><a href="#3-2-应用配置文件" class="headerlink" title="3.2 应用配置文件"></a>3.2 应用配置文件</h2><p>在 <code>resources</code> 目录下，创建 <code>application.yaml</code> 配置文件。配置如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="comment"># datasource 数据源配置内容，对应 DataSourceProperties 配置属性类</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/liquibase_learn?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span> <span class="comment"># 数据库账号</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">root</span> <span class="comment"># 数据库密码</span></span><br><span class="line">  <span class="comment"># Liquibase 配置内容，对应 LiquibaseProperties 配置项</span></span><br><span class="line">  <span class="attr">liquibase:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span> <span class="comment"># 开启 Liquibase 功能。默认为 true 。</span></span><br><span class="line">    <span class="attr">change-log:</span> <span class="string">classpath:/db/changelog/db.changelog-master.yaml</span> <span class="comment"># Liquibase 配置文件地址</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/liquibase_learn?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai</span> <span class="comment"># 数据库地址</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">root</span> <span class="comment"># 数据库账号</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">root</span> <span class="comment"># 数据库密码</span></span><br></pre></td></tr></table></figure><ul><li><code>spring.datasource</code> 配置项，设置数据源的配置。这里暂时没有实际作用，仅仅是为了项目不报数据源的错误。</li><li><code>spring.liquibase</code> 配置项，设置 Liquibase 的属性，而后可以被 <code>LiquibaseAutoConfiguration</code> 自动化配置。<ul><li>每个配置项的作用，胖友自己看下注释。更多的配置项，可以看看 <a href="https://my.oschina.net/cncodemaker/blog/829963" target="_blank" rel="noopener">《Spring Boot 配置属性详解 – Migration》</a> 文章。</li><li>重点看下 <code>change-log</code> 配置项，我们设置了变更日志( Change Log )文件的路径为 <code>&quot;classpath:/db/changelog/db.changelog-master.yaml&quot;</code> 。</li></ul></li></ul><h2 id="3-3-变更文件-Change-Log"><a href="#3-3-变更文件-Change-Log" class="headerlink" title="3.3 变更文件 Change Log"></a>3.3 变更文件 Change Log</h2><p>在 <code>resources/db/changelog</code> 目录下，创建 <code>db.changelog-master.yaml</code> 变更文件。如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">databaseChangeLog:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">changeSet:</span> <span class="comment"># 对应一个 ChangeSet 对象</span></span><br><span class="line">      <span class="attr">id:</span> <span class="number">0</span> <span class="comment"># ChangeSet 编号</span></span><br><span class="line">      <span class="attr">author:</span> <span class="string">calm</span> <span class="comment"># 作者</span></span><br><span class="line">      <span class="attr">comment:</span> <span class="string">空</span> <span class="comment"># 备注</span></span><br></pre></td></tr></table></figure><ul><li>在 <code>databaseChangeLog</code> 配置下，我们可以配置多个 <code>changeSet</code> 配置项。每个 <code>changeSet</code> 配置项，代表一个 变更集( Change Set )。</li><li>这里，我们先配置一个“空”的 <code>changeSet</code> 配置项，方便我们稍后启动项。真正的 <code>changeSet</code> 配置项，我们会按照 Property-based migrations、SQL-based migrations、Java-based migrations 示例顺序，详细解释。</li></ul><h2 id="3-4-LiqiubaseApplication"><a href="#3-4-LiqiubaseApplication" class="headerlink" title="3.4 LiqiubaseApplication"></a>3.4 LiqiubaseApplication</h2><p>创建 <code>LiqiubaseApplication.java</code> 类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// LiqiubaseApplication.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LiqiubaseApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 启动 Spring Boot 应用</span></span><br><span class="line">        SpringApplication.run(LiqiubaseApplication<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动项目。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获得 DATABASECHANGELOGLOCK 数量。此时，该 DATABASECHANGELOGLOCK 表是不存在的。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">01</span><span class="variable">.679</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT COUNT(*) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOGLOCK</span></span><br><span class="line"><span class="comment">// 因为 DATABASECHANGELOGLOCK 表不存在，所以进行创建</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">01</span><span class="variable">.697</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : CREATE TABLE <span class="meta">`lab-20-liquibase`.DATABASECHANGELOGLOCK (ID INT NOT NULL, `LOCKED` BIT(1) NOT NULL, LOCKGRANTED datetime NULL, LOCKEDBY VARCHAR(255) NULL, CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID))</span></span><br><span class="line"><span class="comment">// 重新获得 DATABASECHANGELOGLOCK 数量。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">01</span><span class="variable">.704</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT COUNT(*) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOGLOCK</span></span><br><span class="line"><span class="comment">// 删除 DATABASECHANGELOGLOCK 所有记录。为什么这么做，暂时不造，没去细研究。猜测，避免其他 JVM 进程，异常崩溃，未释放锁</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">01</span><span class="variable">.709</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : DELETE FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOGLOCK</span></span><br><span class="line"><span class="comment">// 插入 DATABASECHANGELOGLOCK 记录，id = 1</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">01</span><span class="variable">.709</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : INSERT INTO <span class="meta">`lab-20-liquibase`.DATABASECHANGELOGLOCK (ID, `LOCKED`) VALUES (1, 0)</span></span><br><span class="line"><span class="comment">// 查询 DATABASECHANGELOGLOCK 记录，id = 1</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">01</span><span class="variable">.711</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT <span class="meta">`LOCKED` FROM `lab-20-liquibase`.DATABASECHANGELOGLOCK WHERE ID=1</span></span><br><span class="line"><span class="comment">// 成功获得到 DATABASECHANGELOGLOCK 锁</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">01</span><span class="variable">.716</span>  INFO <span class="number">65543</span> --- [           main] l<span class="variable">.lockservice</span><span class="variable">.StandardLockService</span>        : Successfully acquired change log lock</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 DATABASECHANGELOG 表</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.485</span>  INFO <span class="number">65543</span> --- [           main] l<span class="variable">.c</span><span class="variable">.StandardChangeLogHistoryService</span>      : Creating database history <span class="keyword">table</span> <span class="keyword">with</span> name: <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.486</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : CREATE TABLE <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG (ID VARCHAR(255) NOT NULL, AUTHOR VARCHAR(255) NOT NULL, FILENAME VARCHAR(255) NOT NULL, DATEEXECUTED datetime NOT NULL, ORDEREXECUTED INT NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35) NULL, `DESCRIPTION` VARCHAR(255) NULL, COMMENTS VARCHAR(255) NULL, TAG VARCHAR(255) NULL, LIQUIBASE VARCHAR(20) NULL, CONTEXTS VARCHAR(255) NULL, LABELS VARCHAR(255) NULL, DEPLOYMENT_ID VARCHAR(10) NULL)</span></span><br><span class="line"><span class="comment">// 获得 DATABASECHANGELOG 数量。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.494</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT COUNT(*) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.496</span>  INFO <span class="number">65543</span> --- [           main] l<span class="variable">.c</span><span class="variable">.StandardChangeLogHistoryService</span>      : Reading from <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG</span></span><br><span class="line"><span class="comment">// 获得 DATABASECHANGELOG 所有记录。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.496</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT * FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC</span></span><br><span class="line"><span class="comment">// 获得 DATABASECHANGELOGLOCK 数量。</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.497</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT COUNT(*) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOGLOCK</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 读取 Change Log 文件的 Change Set 配置</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.500</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : ChangeSet classpath:/db/changelog/db<span class="variable">.changelog</span>-master<span class="variable">.yaml</span>::<span class="number">0</span>::calm ran successfully in <span class="number">1</span>ms</span><br><span class="line"><span class="comment">// 插入 DATABASECHANGELOG 记录，对应 Change Set id = 0 的迁移，执行成功</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.501</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT MAX(ORDEREXECUTED) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.503</span>  INFO <span class="number">65543</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : INSERT INTO <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, `DESCRIPTION`, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('0', 'calm', 'classpath:/db/changelog/db.changelog-master.yaml', NOW(), 1, '8:d41d8cd98f00b204e9800998ecf8427e', 'empty', '空', 'EXECUTED', NULL, NULL, '3.6.3', '3645022498')</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 释放锁</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.506</span>  INFO <span class="number">65543</span> --- [           main] l<span class="variable">.lockservice</span><span class="variable">.StandardLockService</span>        : Successfully released change log lock</span><br><span class="line"></span><br><span class="line"><span class="comment">// 应用启动成功</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">19</span>:<span class="number">37</span>:<span class="number">02</span><span class="variable">.582</span>  INFO <span class="number">65543</span> --- [           main] c<span class="variable">.i</span><span class="variable">.s</span><span class="variable">.l</span><span class="variable">.d</span><span class="variable">.Application</span>                    : Started Application in <span class="number">2</span><span class="variable">.242</span> seconds (JVM running <span class="keyword">for</span> <span class="number">2</span><span class="variable">.675</span>)</span><br></pre></td></tr></table></figure><ul><li>看下在日志内容上的注释。</li><li>在整个日志中，我可以看到 <code>ID = 0</code> 的变更集合( Change Set )完成了执行。</li></ul><p>在启动的日志中，我们看到 Liquibase 会自动创建两张表：</p><ul><li><p><a href="https://www.liquibase.org/documentation/databasechangelog_table.html" target="_blank" rel="noopener">DATABASECHANGELOG</a> 表，数据库变更日志。每一条记录，对应记录每个变更集合( Change Set ) 的执行日志。表结构如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`DATABASECHANGELOG`</span> (</span><br><span class="line">  <span class="string">`ID`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- Change Set 编号</span></span><br><span class="line">  <span class="string">`AUTHOR`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 作者</span></span><br><span class="line">  <span class="string">`FILENAME`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- Change Log 文件路径</span></span><br><span class="line">  <span class="string">`DATEEXECUTED`</span> datetime <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 执行时间</span></span><br><span class="line">  <span class="string">`ORDEREXECUTED`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 执行的顺序</span></span><br><span class="line">  <span class="string">`EXECTYPE`</span> <span class="built_in">varchar</span>(<span class="number">10</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 执行类型。枚举值有 EXECUTED/FAILED/SKIPPED/RERAN/MARK_RAN</span></span><br><span class="line">  <span class="string">`MD5SUM`</span> <span class="built_in">varchar</span>(<span class="number">35</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- MD5 校验码</span></span><br><span class="line">  <span class="string">`DESCRIPTION`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- 描述</span></span><br><span class="line">  <span class="string">`COMMENTS`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- 备注</span></span><br><span class="line">  <span class="string">`TAG`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- Tag 标签</span></span><br><span class="line">  <span class="string">`LIQUIBASE`</span> <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- LIQUIBASE 版本号</span></span><br><span class="line">  <span class="string">`CONTEXTS`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- 上下文</span></span><br><span class="line">  <span class="string">`LABELS`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- Label 标签</span></span><br><span class="line">  <span class="string">`DEPLOYMENT_ID`</span> <span class="built_in">varchar</span>(<span class="number">10</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="comment">-- 部署编号</span></span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8mb4 <span class="keyword">COLLATE</span>=utf8mb4_bin;</span><br></pre></td></tr></table></figure></li><li><p><a href="https://www.liquibase.org/documentation/databasechangeloglock_table.html" target="_blank" rel="noopener">DATABASECHANGELOGLOCK</a> 表，<code>DATABASECHANGELOG</code> 表的锁，用于确保多个 JVM 进程，同时 Liquibase 尝试变更数据库。表结构如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`DATABASECHANGELOGLOCK`</span> (</span><br><span class="line">  <span class="string">`ID`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 锁的编号。</span></span><br><span class="line">  <span class="string">`LOCKED`</span> <span class="built_in">bit</span>(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>, <span class="comment">-- 是否锁。1-锁，0-未锁</span></span><br><span class="line">  <span class="string">`LOCKGRANTED`</span> datetime <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- 获得锁的时间</span></span><br><span class="line">  <span class="string">`LOCKEDBY`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin  <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>, <span class="comment">-- 锁定人</span></span><br><span class="line">  PRIMARY <span class="keyword">KEY</span> (<span class="string">`ID`</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8mb4 <span class="keyword">COLLATE</span>=utf8mb4_bin;</span><br></pre></td></tr></table></figure><ul><li>目前，只有一条记录。也就是说，有且仅有一个 JVM 进程获得 DATABASECHANGELOG 表的锁成功。</li></ul></li></ul><p>此时，我们去查询下 MySQL 。如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SELECT * FROM DATABASECHANGELOG;</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| ID | AUTHOR | FILENAME                                         | DATEEXECUTED        | ORDEREXECUTED | EXECTYPE | MD5SUM                             | DESCRIPTION | COMMENTS | TAG  | LIQUIBASE | CONTEXTS | LABELS | DEPLOYMENT_ID |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| 0  | calm  | classpath:/db/changelog/db.changelog-master.yaml | 2019-11-16 19:37:02 |             1 | EXECUTED | 8:d41d8cd98f00b204e9800998ecf8427e | empty       |   空  | NULL | 3.6.3     | NULL     | NULL   | 3645022498    |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">1 row in <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 查看到一条 `ID = 0` 的 `DATABASECHANGELOG` 记录，表示变更集合( Change Set ) `ID = 0` 完成了执行。</span></span><br><span class="line"></span><br><span class="line">mysql&gt; <span class="keyword">SELECT</span> * <span class="keyword">FROM</span> DATABASECHANGELOGLOCK;</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">| ID | LOCKED | LOCKGRANTED | LOCKEDBY |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">|  1 | 0      | NULL        | NULL     |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">1 row in <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 查看到一条处于释放状态的锁记录。</span></span><br></pre></td></tr></table></figure><p>再次启动项目。执行日志如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 获得 DATABASECHANGELOGLOCK 数量。</span><br><span class="line">2020-02-17 20:22:24.589  INFO 66473 --- [           main] liquibase.executor.jvm.JdbcExecutor      : SELECT COUNT(*) FROM &#96;lab-20-liquibase&#96;.DATABASECHANGELOGLOCK</span><br><span class="line">2020-02-17 20:22:24.594  INFO 66473 --- [           main] liquibase.executor.jvm.JdbcExecutor      : SELECT COUNT(*) FROM &#96;lab-20-liquibase&#96;.DATABASECHANGELOGLOCK</span><br><span class="line">&#x2F;&#x2F; 查询 DATABASECHANGELOGLOCK 记录，id &#x3D; 1</span><br><span class="line">2020-02-17 20:22:24.595  INFO 66473 --- [           main] liquibase.executor.jvm.JdbcExecutor      : SELECT &#96;LOCKED&#96; FROM &#96;lab-20-liquibase&#96;.DATABASECHANGELOGLOCK WHERE ID&#x3D;1</span><br><span class="line">&#x2F;&#x2F; 成功获得到 DATABASECHANGELOGLOCK 锁</span><br><span class="line">2020-02-17 20:22:24.603  INFO 66473 --- [           main] l.lockservice.StandardLockService        : Successfully acquired change log lock</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 获得一条 DATABASECHANGELOG 。过滤 MD5SUM 非空且一条记录，有点奇怪，后面在细细研究。</span><br><span class="line">2020-02-17 20:22:25.378  INFO 66473 --- [           main] liquibase.executor.jvm.JdbcExecutor      : SELECT MD5SUM FROM &#96;lab-20-liquibase&#96;.DATABASECHANGELOG WHERE MD5SUM IS NOT NULL LIMIT 1</span><br><span class="line">&#x2F;&#x2F; 获得 DATABASECHANGELOG 数量。</span><br><span class="line">2020-02-17 20:22:25.379  INFO 66473 --- [           main] liquibase.executor.jvm.JdbcExecutor      : SELECT COUNT(*) FROM &#96;lab-20-liquibase&#96;.DATABASECHANGELOG</span><br><span class="line">&#x2F;&#x2F; 获得 DATABASECHANGELOG 所有记录。</span><br><span class="line">2020-02-17 20:22:25.380  INFO 66473 --- [           main] l.c.StandardChangeLogHistoryService      : Reading from &#96;lab-20-liquibase&#96;.DATABASECHANGELOG</span><br><span class="line">2020-02-17 20:22:25.380  INFO 66473 --- [           main] liquibase.executor.jvm.JdbcExecutor      : SELECT * FROM &#96;lab-20-liquibase&#96;.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 释放锁</span><br><span class="line">2020-02-17 20:22:25.385  INFO 66473 --- [           main] l.lockservice.StandardLockService        : Successfully released change log lock</span><br><span class="line"></span><br><span class="line">2020-02-17 20:22:25.447  INFO 66473 --- [           main] c.i.s.l.d.Application                    : Started Application in 2.215 seconds (JVM running for 2.62)</span><br></pre></td></tr></table></figure><ul><li>在整个日志中，我可以看到 <code>ID = 0</code> 的变更集合( Change Set )不会重复执行。</li></ul><p>下面，我们修改 <code>ID = 0</code> 的变更集合( Change Set )，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">databaseChangeLog:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">changeSet:</span> <span class="comment"># 对应一个 ChangeSet 对象</span></span><br><span class="line">      <span class="attr">id:</span> <span class="number">0</span> <span class="comment"># ChangeSet 编号</span></span><br><span class="line">      <span class="attr">author:</span> <span class="string">calm</span> <span class="comment"># 作者</span></span><br><span class="line">      <span class="attr">comment:</span> <span class="string">空</span> <span class="comment"># 备注</span></span><br><span class="line">      <span class="attr">changes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">createTable:</span> <span class="comment"># 创建表，对应 CreateTableChange 对象。</span></span><br><span class="line">            <span class="attr">tableName:</span> <span class="string">users</span> <span class="comment"># 表名</span></span><br><span class="line">            <span class="attr">remarkds:</span> <span class="string">用户表</span> <span class="comment"># 表注释</span></span><br><span class="line">            <span class="attr">columns:</span> <span class="comment"># 对应 ColumnConfig 数组</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                  <span class="attr">name:</span> <span class="string">id</span> <span class="comment"># 字段名</span></span><br><span class="line">                  <span class="attr">type:</span> <span class="string">int</span> <span class="comment"># 字段类型</span></span><br><span class="line">                  <span class="attr">autoIncrement:</span> <span class="literal">true</span> <span class="comment"># 自增</span></span><br><span class="line">                  <span class="attr">constraints:</span> <span class="comment"># 限制条件，对应一个 ConstraintsConfig 对象</span></span><br><span class="line">                    <span class="attr">primaryKey:</span> <span class="literal">true</span> <span class="comment"># 主键</span></span><br><span class="line">                    <span class="attr">nullable:</span> <span class="literal">false</span> <span class="comment"># 不允许空</span></span><br></pre></td></tr></table></figure><ul><li><p>增加了 <code>changes</code> 配置项。</p></li><li><p>我们再再再次启动项目。会报如下错误：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Caused by: liquibase.exception.ValidationFailedException: Validation Failed:</span><br><span class="line">     1 change sets check sum</span><br><span class="line">          classpath:&#x2F;db&#x2F;changelog&#x2F;db.changelog-master.yaml::0::calm was: 8:d41d8cd98f00b204e9800998ecf8427e but is now: 8:2fb4d9484c5982a97eb8d580ba934395</span><br></pre></td></tr></table></figure><ul><li>我们会发现，Liquibase 会给每个变更集合( Change Set )，计算出一个 <code>MD5SUM</code> 字段。这样，每次启动时，都会校验已经执行的变更集合( Change Set )，是否发生了改变。如果是，抛出异常。这样，保证不会因为 变更集合( Change Set ) 变更，导致出现问题。</li></ul></li><li><p>测试完成，我们还是将 <code>ID = 0</code> 的变更集合( Change Set ) 恢复原样。</p></li></ul><h2 id="3-5-Property-based-migrations"><a href="#3-5-Property-based-migrations" class="headerlink" title="3.5 Property-based migrations"></a>3.5 Property-based migrations</h2><p>修改 <code>db.changelog-master.yaml</code> 变更日志，增加 <code>ID = 1</code> 的变更集合( Change Set ) 。代码如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">changeSet:</span> <span class="comment"># 对应一个 ChangeSet 对象</span></span><br><span class="line">    <span class="attr">id:</span> <span class="number">1</span> <span class="comment"># ChangeSet 编号</span></span><br><span class="line">    <span class="attr">author:</span> <span class="string">calm</span> <span class="comment"># 作者</span></span><br><span class="line">    <span class="attr">comment:</span> <span class="string">初始化</span> <span class="string">users</span> <span class="string">表</span> <span class="comment"># 备注</span></span><br><span class="line">    <span class="attr">changes:</span> <span class="comment"># 对应 Change 数组。Change 是一个接口，每种操作对应一种 Change 实现类</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">createTable:</span> <span class="comment"># 创建表，对应 CreateTableChange 对象。</span></span><br><span class="line">          <span class="attr">tableName:</span> <span class="string">users</span> <span class="comment"># 表名</span></span><br><span class="line">          <span class="attr">remarkds:</span> <span class="string">用户表</span> <span class="comment"># 表注释</span></span><br><span class="line">          <span class="attr">columns:</span> <span class="comment"># 对应 ColumnConfig 数组</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">id</span> <span class="comment"># 字段名</span></span><br><span class="line">                <span class="attr">type:</span> <span class="string">int</span> <span class="comment"># 字段类型</span></span><br><span class="line">                <span class="attr">autoIncrement:</span> <span class="literal">true</span> <span class="comment"># 自增</span></span><br><span class="line">                <span class="attr">constraints:</span> <span class="comment"># 限制条件，对应一个 ConstraintsConfig 对象</span></span><br><span class="line">                  <span class="attr">primaryKey:</span> <span class="literal">true</span> <span class="comment"># 主键</span></span><br><span class="line">                  <span class="attr">nullable:</span> <span class="literal">false</span> <span class="comment"># 不允许空</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">username</span></span><br><span class="line">                <span class="attr">type:</span> <span class="string">varchar(64)</span></span><br><span class="line">                <span class="attr">constraints:</span></span><br><span class="line">                  <span class="attr">nullable:</span> <span class="literal">false</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">password</span></span><br><span class="line">                <span class="attr">type:</span> <span class="string">varchar(32)</span></span><br><span class="line">                <span class="attr">constraints:</span></span><br><span class="line">                  <span class="attr">nullable:</span> <span class="literal">false</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">create_time</span></span><br><span class="line">                <span class="attr">type:</span> <span class="string">datetime</span></span><br><span class="line">                <span class="attr">constraints:</span></span><br><span class="line">                  <span class="attr">nullable:</span> <span class="literal">false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">insert:</span> <span class="comment"># 插入记录，对应 InsertDataChange 对象。</span></span><br><span class="line">          <span class="attr">tableName:</span> <span class="string">users</span> <span class="comment"># 表名</span></span><br><span class="line">          <span class="attr">columns:</span> <span class="comment"># 对应 ColumnConfig 数组</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">username</span> <span class="comment"># 字段名</span></span><br><span class="line">                <span class="attr">value:</span> <span class="string">calm</span> <span class="comment"># 值</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">password</span></span><br><span class="line">                <span class="attr">value:</span> <span class="string">password</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">column:</span></span><br><span class="line">                <span class="attr">name:</span> <span class="string">create_time</span></span><br><span class="line">                <span class="attr">value:</span> <span class="string">now()</span></span><br></pre></td></tr></table></figure><ul><li>在这个变更集合( Change Set )中，我们添加了两个变更( Change )，分别用于创建 <code>users</code> 表、插入一条 <code>users</code> 记录。</li><li>Liquibase 基于上述的配置，自动生成对应的 SQL 操作。</li></ul><p>启动项目。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ... 省略雷同日志</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 `users` 表</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">21</span>:<span class="number">42</span><span class="variable">.317</span>  INFO <span class="number">67111</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : CREATE TABLE <span class="meta">`lab-20-liquibase`.users (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(64) NOT NULL, password VARCHAR(32) NOT NULL, create_time datetime NOT NULL, CONSTRAINT PK_USERS PRIMARY KEY (id))</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">21</span>:<span class="number">42</span><span class="variable">.323</span>  INFO <span class="number">67111</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : Table users created</span><br><span class="line"></span><br><span class="line"><span class="comment">// 插入一条 `users` 表的记录</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">21</span>:<span class="number">42</span><span class="variable">.323</span>  INFO <span class="number">67111</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : INSERT INTO <span class="meta">`lab-20-liquibase`.users (username, password, create_time) VALUES ('yudaoyuanma', 'password', now())</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">21</span>:<span class="number">42</span><span class="variable">.324</span>  INFO <span class="number">67111</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : New row inserted into users</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出 Change Log 文件的 Change Set 全部执行成功</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">21</span>:<span class="number">42</span><span class="variable">.325</span>  INFO <span class="number">67111</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : ChangeSet classpath:/db/changelog/db<span class="variable">.changelog</span>-master<span class="variable">.yaml</span>::<span class="number">1</span>::calm ran successfully in <span class="number">10</span>ms</span><br><span class="line"><span class="comment">// 插入 DATABASECHANGELOG 记录，对应 Change Set id = 1 的迁移，执行成功</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">21</span>:<span class="number">42</span><span class="variable">.326</span>  INFO <span class="number">67111</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT MAX(ORDEREXECUTED) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">21</span>:<span class="number">42</span><span class="variable">.327</span>  INFO <span class="number">67111</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : INSERT INTO <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, `DESCRIPTION`, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('1', 'calm', 'classpath:/db/changelog/db.changelog-master.yaml', NOW(), 3, '8:1dcffd4c4f87b02e3758bf6b4ecea471', 'createTable tableName=users; insert tableName=users', '', 'EXECUTED', NULL, NULL, '3.6.3', '3651302309')</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 省略雷同日志</span></span><br></pre></td></tr></table></figure><ul><li>看下日志内容上的注释。</li><li>在整个日志中，我可以看到 ID = 1 的变更集合( Change Set )完成了执行。在这个过程中，我们可以看到两条分别用于创建 <code>users</code> 表和插入 <code>users</code> 记录的 SQL 。</li></ul><p>此时，我们去查询下 MySQL 。如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SELECT * FROM DATABASECHANGELOG;</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| ID | AUTHOR | FILENAME                                         | DATEEXECUTED        | ORDEREXECUTED | EXECTYPE | MD5SUM                             | DESCRIPTION                                         | COMMENTS | TAG  | LIQUIBASE | CONTEXTS | LABELS | DEPLOYMENT_ID |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| 0  | calm  | classpath:/db/changelog/db.changelog-master.yaml | 2019-11-16 21:35:09 |             1 | EXECUTED | 8:d41d8cd98f00b204e9800998ecf8427e | empty                                               |    空      | NULL | 3.6.3     | NULL     | NULL   | 3652109350    |</span><br><span class="line">| 1  | calm  | classpath:/db/changelog/db.changelog-master.yaml | 2019-11-16 21:35:09 |             2 | EXECUTED | 8:1dcffd4c4f87b02e3758bf6b4ecea471 | createTable tableName=users; <span class="keyword">insert</span> tableName=<span class="keyword">users</span> |     初始化 <span class="keyword">users</span> 表     | <span class="literal">NULL</span> | <span class="number">3.6</span><span class="number">.3</span>     | <span class="literal">NULL</span>     | <span class="literal">NULL</span>   | <span class="number">3652109350</span>    |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 多查看到一条 `ID = 1` 的 `DATABASECHANGELOG` 记录，表示变更集合( Change Set ) `ID = 1` 完成了执行。并且，在描述中，我们可以看到对该更集合( Change Set )的简单说明。</span></span><br><span class="line"></span><br><span class="line">mysql&gt; <span class="keyword">SELECT</span> * <span class="keyword">FROM</span> DATABASECHANGELOGLOCK;</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">| ID | LOCKED | LOCKGRANTED | LOCKEDBY |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">|  1 | 0      | NULL        | NULL     |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">1 row in <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 查看到一条处于释放状态的锁记录。</span></span><br></pre></td></tr></table></figure><p>使用 Property-based migrations 方式，我们无需编写相应的 SQL 。虽然在编写上有些啰嗦，但是易读性还是不错的。</p><h2 id="3-6-SQL-based-migrations"><a href="#3-6-SQL-based-migrations" class="headerlink" title="3.6 SQL-based migrations"></a>3.6 SQL-based migrations</h2><p>修改 <code>db.changelog-master.yaml</code>变更日志，增加 <code>ID = 2</code> 的变更集合( Change Set ) 。代码如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">changeSet:</span> <span class="comment"># 对应一个 ChangeSet 对象</span></span><br><span class="line">    <span class="attr">id:</span> <span class="number">2</span> <span class="comment"># ChangeSet 编号</span></span><br><span class="line">    <span class="attr">author:</span> <span class="string">calm</span> <span class="comment"># 作者</span></span><br><span class="line">    <span class="attr">comment:</span> <span class="string">初始化</span> <span class="string">users2</span> <span class="string">表</span> <span class="comment"># 备注</span></span><br><span class="line">    <span class="attr">changes:</span> <span class="comment"># 对应 Change 数组。Change 是一个接口，每种操作对应一种 Change 实现类</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">sqlFile:</span> <span class="comment"># 使用 SQL 文件，对应 SQLFileChange 对象</span></span><br><span class="line">          <span class="attr">encoding:</span> <span class="string">utf8</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">classpath:db/changelog/sqlfile/CHAGE_SET_2_INIT_DB.sql</span></span><br></pre></td></tr></table></figure><ul><li>这个示例，我们实现的是和 <a href="https://calm_java.gitee.io/blog/2020/02/13/labs-springboot2-database-version-control/">「3.5 Property-based migrations」</a> 一样的效果，只是操作的是 <code>users2</code> 表。</li><li>对应文档为 <a href="https://www.liquibase.org/documentation/changes/sql_file.html" target="_blank" rel="noopener">《Liquibase 文档 —— SQL FILE》</a> 。</li><li>通过在 <code>sqlFile</code> 配置项的 <code>path</code> 属性，设置使用自定义 SQL 文件的路径。</li></ul><p>在 <code>resources/db/changelog/sqlfile</code> 目录下，创建 <code>CHAGE_SET_2_INIT_DB.sql</code> 变更文件。如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建用户表</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`users2`</span> (</span><br><span class="line">  <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT <span class="keyword">COMMENT</span> <span class="string">'用户编号'</span>,</span><br><span class="line">  <span class="string">`username`</span> <span class="built_in">varchar</span>(<span class="number">64</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'账号'</span>,</span><br><span class="line">  <span class="string">`password`</span> <span class="built_in">varchar</span>(<span class="number">32</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'密码'</span>,</span><br><span class="line">  <span class="string">`create_time`</span> datetime <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'创建时间'</span>,</span><br><span class="line">  PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>),</span><br><span class="line">  <span class="keyword">UNIQUE</span> <span class="keyword">KEY</span> <span class="string">`idx_username`</span> (<span class="string">`username`</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> AUTO_INCREMENT=<span class="number">7</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8mb4 <span class="keyword">COLLATE</span>=utf8mb4_bin;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入一条数据</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="string">`users2`</span>(username, <span class="keyword">password</span>, create_time) <span class="keyword">VALUES</span>(<span class="string">'calm'</span>, <span class="string">'password'</span>, <span class="keyword">now</span>());</span><br></pre></td></tr></table></figure><ul><li>这里的 SQL ，和我们在 <a href="https://calm_java.gitee.io/blog/2020/02/13/labs-springboot2-database-version-control/">「2.4 SQL-based migrations」</a> 是一样的。</li></ul><p>启动项目。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ... 省略雷同日志</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行 CHAGE_SET_2_INIT_DB.sql 里的 SQL</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span><span class="variable">.793</span>  INFO <span class="number">67368</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : -- 创建用户表</span><br><span class="line">CREATE TABLE <span class="meta">`users2` (</span></span><br><span class="line">  <span class="meta">`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',</span></span><br><span class="line">  <span class="meta">`username` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号',</span></span><br><span class="line">  <span class="meta">`password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',</span></span><br><span class="line">  <span class="meta">`create_time` datetime DEFAULT NULL COMMENT '创建时间',</span></span><br><span class="line">  PRIMARY KEY (<span class="meta">`id`),</span></span><br><span class="line">  UNIQUE KEY <span class="meta">`idx_username` (`username`)</span></span><br><span class="line">) ENGINE=InnoDB AUTO_INCREMENT=<span class="number">7</span> DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span><span class="variable">.800</span>  INFO <span class="number">67368</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : -- 插入一条数据</span><br><span class="line">INSERT INTO <span class="meta">`users2`(username, password, create_time) VALUES('calm', 'password', now())</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span><span class="variable">.801</span>  INFO <span class="number">67368</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : SQL in file classpath:db/changelog/sqlfile/CHAGE_SET_2_INIT_DB<span class="variable">.sql</span> executed</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出 Change Log 文件的 Change Set 全部执行成功</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span><span class="variable">.802</span>  INFO <span class="number">67368</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : ChangeSet classpath:/db/changelog/db<span class="variable">.changelog</span>-master<span class="variable">.yaml</span>::<span class="number">2</span>::<span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span><span class="variable">.802</span>  INFO <span class="number">67368</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : ChangeSet classpath:/db/changelog/db<span class="variable">.changelog</span>-master<span class="variable">.yaml</span>::<span class="number">2</span>::calm ran successfully in <span class="number">13</span>ms</span><br><span class="line"></span><br><span class="line"> ran successfully in <span class="number">13</span>ms</span><br><span class="line"></span><br><span class="line"><span class="comment">// 插入 DATABASECHANGELOG 记录，对应 Change Set id = 2 的迁移，执行成功</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span><span class="variable">.802</span>  INFO <span class="number">67368</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT MAX(ORDEREXECUTED) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span><span class="variable">.804</span>  INFO <span class="number">67368</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : INSERT INTO <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, `DESCRIPTION`, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('2', 'calm', 'classpath:/db/changelog/db.changelog-master.yaml', NOW(), 3, '8:c224782d41ad7fac92699ebf392c7f5e', 'sqlFile', '', 'EXECUTED', NULL, NULL, '3.6.3', '3652405782')</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 省略雷同日志</span></span><br></pre></td></tr></table></figure><ul><li>看下日志内容上的注释。</li><li>在整个日志中，我可以看到 ID = 1 的变更集合( Change Set )完成了执行。在这个过程中，我们可以看到 <code>CHAGE_SET_2_INIT_DB.sql</code> 的 SQL 被执行。</li></ul><p>此时，我们去查询下 MySQL 。如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SELECT * FROM DATABASECHANGELOG;</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| ID | AUTHOR | FILENAME                                         | DATEEXECUTED        | ORDEREXECUTED | EXECTYPE | MD5SUM                             | DESCRIPTION                                         | COMMENTS | TAG  | LIQUIBASE | CONTEXTS | LABELS | DEPLOYMENT_ID |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| 0  | calm  | classpath:/db/changelog/db.changelog-master.yaml | 2019-11-16 21:35:09 |             1 | EXECUTED | 8:d41d8cd98f00b204e9800998ecf8427e | empty                                               |          | NULL | 3.6.3     | NULL     | NULL   | 3652109350    |</span><br><span class="line">| 1  | calm  | classpath:/db/changelog/db.changelog-master.yaml | 2019-11-16 21:35:09 |             2 | EXECUTED | 8:1dcffd4c4f87b02e3758bf6b4ecea471 | createTable tableName=users; <span class="keyword">insert</span> tableName=<span class="keyword">users</span> |          | <span class="literal">NULL</span> | <span class="number">3.6</span><span class="number">.3</span>     | <span class="literal">NULL</span>     | <span class="literal">NULL</span>   | <span class="number">3652109350</span>    |</span><br><span class="line">| <span class="number">2</span>  | calm  | classpath:/db/changelog/db.changelog-master.yaml | <span class="number">2019</span><span class="number">-11</span><span class="number">-16</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span> |             <span class="number">3</span> | EXECUTED | <span class="number">8</span>:c224782d41ad7fac92699ebf392c7f5e | sqlFile                                             |          | <span class="literal">NULL</span> | <span class="number">3.6</span><span class="number">.3</span>     | <span class="literal">NULL</span>     | <span class="literal">NULL</span>   | <span class="number">3652405782</span>    |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 多查看到一条 `ID = 2` 的 `DATABASECHANGELOG` 记录，表示变更集合( Change Set ) `ID = 2` 完成了执行。并且，在描述中，我们可以看到 `sqlFile` 。</span></span><br><span class="line"></span><br><span class="line">mysql&gt; <span class="keyword">SELECT</span> * <span class="keyword">FROM</span> DATABASECHANGELOGLOCK;</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">| ID | LOCKED | LOCKGRANTED | LOCKEDBY |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">|  1 | 0      | NULL        | NULL     |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">1 row in <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 查看到一条处于释放状态的锁记录。</span></span><br></pre></td></tr></table></figure><p>相比 Property-based migrations 方式来说，卡姆更愿意使用 SQL-based migrations 。毕竟，我们更加熟悉 SQL 语法，交给 DBA 审计也更加方便。</p><h2 id="3-7-Java-based-migrations"><a href="#3-7-Java-based-migrations" class="headerlink" title="3.7 Java-based migrations"></a>3.7 Java-based migrations</h2><p>在 <code>cn.javadog.labs.springboot2.liquibase.migration</code> 包路径下，创建 <code>CHANGE_SET_3_FixUsername.java</code> 类，修复 <code>users</code> 的用户名。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CHANGE_SET_3_FixUsername.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CHANGE_SET_3_FixUsername</span> <span class="keyword">implements</span> <span class="title">CustomTaskChange</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Database database)</span> <span class="keyword">throws</span> CustomChangeException </span>&#123;</span><br><span class="line">        JdbcConnection connection = (JdbcConnection) database.getConnection();</span><br><span class="line">        <span class="keyword">try</span> (PreparedStatement psmt = connection.prepareStatement(<span class="string">"SELECT id, username, password, create_time FROM users"</span>)) &#123;</span><br><span class="line">            <span class="keyword">try</span> (ResultSet rs = psmt.executeQuery()) &#123;</span><br><span class="line">                <span class="keyword">while</span> (rs.next()) &#123;</span><br><span class="line">                    String username = rs.getString(<span class="string">"username"</span>);</span><br><span class="line">                    <span class="keyword">if</span> (<span class="string">"calm"</span>.equals(username)) &#123;</span><br><span class="line">                        Integer id = rs.getInt(<span class="string">"id"</span>);</span><br><span class="line">                        <span class="keyword">try</span> (PreparedStatement updatePsmt = connection.prepareStatement(<span class="string">"UPDATE users SET "</span> +</span><br><span class="line">                            <span class="string">"username = ? WHERE id = ?"</span>))&#123;</span><br><span class="line">                            updatePsmt.setString(<span class="number">1</span>, <span class="string">"calm pro"</span>);</span><br><span class="line">                            updatePsmt.setInt(<span class="number">2</span>, id);</span><br><span class="line">                            updatePsmt.executeUpdate();</span><br><span class="line">                            logger.info(<span class="string">"[migrate][更新 user(&#123;&#125;) 的用户名(&#123;&#125; =&gt; &#123;&#125;)"</span>, id, username, <span class="string">"calm pro"</span>);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getConfirmationMessage</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> SetupException </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setFileOpener</span><span class="params">(ResourceAccessor resourceAccessor)</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ValidationErrors <span class="title">validate</span><span class="params">(Database database)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>通过实现 CustomTaskChange 接口，实现 Java 自定义迁移逻辑。</li><li>这里有一点比较蛋疼，在 <code>#execute(Database database)</code> 方法中，提供给我们的是 Database 对象，无法方便的获得 DataSource 对象，从而使用 Spring JdbcTemplate 。</li></ul><p>修改 <code>db.changelog-master.yaml</code> 变更日志，增加 <code>ID = 3</code> 的变更集合( Change Set ) 。代码如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- changeSet: # 对应一个 ChangeSet 对象</span><br><span class="line">    id: 3 # ChangeSet 编号</span><br><span class="line">    author: calm # 作者</span><br><span class="line">    comment: 修复 &#96;users&#96; 的用户名 # 备注</span><br><span class="line">    changes: # 对应 Change 数组。Change 是一个接口，每种操作对应一种 Change 实现类</span><br><span class="line">      - customChange: &#123;class: cn.javadog.labs.springboot2.liquibase.migration.CHANGE_SET_3_FixUsername&#125; # 对应 CustomTaskChange</span><br></pre></td></tr></table></figure><ul><li>对应文档为 <a href="https://www.liquibase.org/documentation/changes/custom_change.html" target="_blank" rel="noopener">《Liquibase 文档 —— Custom Change》</a> 。</li><li>通过在 <code>customChange</code> 配置项的 <code>class</code> 属性，设置使用自定义 CustomTaskChange 的实现的类名。</li></ul><p>启动项目。执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ... 省略雷同日志</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// CHANGE_SET_3_FixUsername 的执行日志</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">56</span>:<span class="number">48</span><span class="variable">.782</span>  INFO <span class="number">67585</span> --- [           main] c<span class="variable">.i</span><span class="variable">.s</span><span class="variable">.l</span><span class="variable">.d</span><span class="variable">.m</span><span class="variable">.CHANGE_SET_3_FixUsername</span>     : [migrate][更新 user(<span class="number">1</span>) 的用户名(yudaoyuanma =&gt; yutou)</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">56</span>:<span class="number">48</span><span class="variable">.782</span>  INFO <span class="number">67585</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : <span class="literal">null</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 插入 DATABASECHANGELOG 记录，对应 Change Set id = 3 的迁移，执行成功</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">56</span>:<span class="number">48</span><span class="variable">.783</span>  INFO <span class="number">67585</span> --- [           main] liquibase<span class="variable">.changelog</span><span class="variable">.ChangeSet</span>            : ChangeSet classpath:/db/changelog/db<span class="variable">.changelog</span>-master<span class="variable">.yaml</span>::<span class="number">3</span>::calm ran successfully in <span class="number">3</span>ms</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">56</span>:<span class="number">48</span><span class="variable">.783</span>  INFO <span class="number">67585</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : SELECT MAX(ORDEREXECUTED) FROM <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">17</span> <span class="number">21</span>:<span class="number">56</span>:<span class="number">48</span><span class="variable">.784</span>  INFO <span class="number">67585</span> --- [           main] liquibase<span class="variable">.executor</span><span class="variable">.jvm</span><span class="variable">.JdbcExecutor</span>      : INSERT INTO <span class="meta">`lab-20-liquibase`.DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, `DESCRIPTION`, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('3', 'calm', 'classpath:/db/changelog/db.changelog-master.yaml', NOW(), 4, '8:2595b6826984b91149063782c4cd6c29', 'customChange', '', 'EXECUTED', NULL, NULL, '3.6.3', '3653408775')</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 省略雷同日志</span></span><br></pre></td></tr></table></figure><ul><li>看下日志内容上的注释。</li><li>在整个日志中，我可以看到 ID = 3 的变更集合( Change Set )完成了执行。在这个过程中，我们可以看不到 CHANGE_SET_3_FixUsername 的任何信息，除了我们打的 INFO 日志。所以，在使用 Java-based migrations 方式时，最好自己打下日志，方便排查。</li></ul><p>此时，我们去查询下 MySQL 。如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SELECT * FROM DATABASECHANGELOG;</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| ID | AUTHOR | FILENAME                                         | DATEEXECUTED        | ORDEREXECUTED | EXECTYPE | MD5SUM                             | DESCRIPTION                                         | COMMENTS | TAG  | LIQUIBASE | CONTEXTS | LABELS | DEPLOYMENT_ID |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line">| 0  | calm  | classpath:/db/changelog/db.changelog-master.yaml | 2019-11-16 21:35:09 |             1 | EXECUTED | 8:d41d8cd98f00b204e9800998ecf8427e | empty                                               |          | NULL | 3.6.3     | NULL     | NULL   | 3652109350    |</span><br><span class="line">| 1  | calm  | classpath:/db/changelog/db.changelog-master.yaml | 2019-11-16 21:35:09 |             2 | EXECUTED | 8:1dcffd4c4f87b02e3758bf6b4ecea471 | createTable tableName=users; <span class="keyword">insert</span> tableName=<span class="keyword">users</span> |          | <span class="literal">NULL</span> | <span class="number">3.6</span><span class="number">.3</span>     | <span class="literal">NULL</span>     | <span class="literal">NULL</span>   | <span class="number">3652109350</span>    |</span><br><span class="line">| <span class="number">2</span>  | calm  | classpath:/db/changelog/db.changelog-master.yaml | <span class="number">2019</span><span class="number">-11</span><span class="number">-16</span> <span class="number">21</span>:<span class="number">40</span>:<span class="number">05</span> |             <span class="number">3</span> | EXECUTED | <span class="number">8</span>:c224782d41ad7fac92699ebf392c7f5e | sqlFile                                             |          | <span class="literal">NULL</span> | <span class="number">3.6</span><span class="number">.3</span>     | <span class="literal">NULL</span>     | <span class="literal">NULL</span>   | <span class="number">3652405782</span>    |</span><br><span class="line">+<span class="comment">----+--------+--------------------------------------------------+---------------------+---------------+----------+------------------------------------+-----------------------------------------------------+----------+------+-----------+----------+--------+---------------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 多查看到一条 `ID = 2` 的 `DATABASECHANGELOG` 记录，表示变更集合( Change Set ) `ID = 2` 完成了执行。并且，在描述中，我们可以看到 `sqlFile` 。</span></span><br><span class="line"></span><br><span class="line">mysql&gt; <span class="keyword">SELECT</span> * <span class="keyword">FROM</span> DATABASECHANGELOGLOCK;</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">| ID | LOCKED | LOCKGRANTED | LOCKEDBY |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">|  1 | 0      | NULL        | NULL     |</span><br><span class="line">+<span class="comment">----+--------+-------------+----------+</span></span><br><span class="line">1 row in <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="comment"># 查看到一条处于释放状态的锁记录。</span></span><br></pre></td></tr></table></figure><h2 id="3-8-其它功能"><a href="#3-8-其它功能" class="headerlink" title="3.8 其它功能"></a>3.8 其它功能</h2><p>在上述的示例，我们是基于 <a href="https://www.liquibase.org/documentation/spring.html" target="_blank" rel="noopener">Spring Boot</a> 的使用方式。而 Flyway 还提供了如下方式：</p><ul><li><a href="https://www.liquibase.org/javadoc/index.html" target="_blank" rel="noopener">Java API</a> 直接使用 Java 原生的 API 。</li><li><a href="https://www.liquibase.org/documentation/command_line.html" target="_blank" rel="noopener">Command Line</a> 命令行工具</li><li><a href="https://www.liquibase.org/documentation/ant/index.html" target="_blank" rel="noopener">Ant</a> 任务</li><li><a href="https://www.liquibase.org/documentation/maven/index.html" target="_blank" rel="noopener">Maven</a> 插件</li><li><a href="https://juejin.im/post/5cb19c0b6fb9a068646c0948#heading-12" target="_blank" rel="noopener">Gralde</a> 插件</li><li>… 等等</li></ul><p>Liquibase 还有一些其它细节，建议抽时间，通读下 <a href="https://www.liquibase.org/documentation/index.html" target="_blank" rel="noopener">Documentation</a> 文档。</p><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><blockquote><p>两款数据库迁移工具其实定位上是差别的，一般我的倾向是小项目，整体变动不大的用 Flyway ，而大应用和企业应用用 Liquibase 更合适。</p></blockquote><ul><li>不过从入门看下来，Liquibase 并未看到比 Flyway 带来多大的优势，反倒 Flyway 基于“约定大于配置”的思想，使用上更加便捷。欢迎胖友能够解答下艿艿在这块的疑惑。</li></ul><p>关于最佳实践，目前找到比较合适的两篇，如下：</p><ul><li><a href="https://segmentfault.com/a/1190000016641122" target="_blank" rel="noopener">《Spring Boot 使用 Liquibase 最佳实》</a></li><li><a href="https://www.cnblogs.com/harrychinese/p/springboot_flyway.html" target="_blank" rel="noopener">《Spring Boot 系列: 使用 flyway 管理数据库版本》</a> 文末的 flyway 最佳实践的部分。</li></ul><p>在实际项目使用时，可能还需要额外考虑一些事情，例如说：</p><ul><li>大表直接添加索引，直接导致线上故障。</li><li>内部有 DBA ，数据库调整需要内部审核。</li></ul><blockquote><p>思路都是一致的，只是形态不同。</p></blockquote><ul><li>1、创建迁移 Migration 项目，专门编写数据相关变更的脚本。然后连接各种环境，本机手动运行。</li><li>2、在定时任务项目中，创建迁移 Migration Job 类，编写数据变更的脚本。然后，部署到服务器上，通过调度中心触发运行。</li><li>3、在业务项目中，创建迁移 API 接口，编写数据变更的脚本。然后，部署到服务器上，通过调用该 API 接口运行。</li></ul></div><div class="article-footer"><blockquote class="mt-2x"><ul class="post-copyright list-unstyled"><li class="post-copyright-link hidden-xs"><strong>本文链接：</strong> <a href="https://calm_java.gitee.io/blog/2020/02/17/labs-springboot2-database-version-control/" title="Spring Boot 数据库版本管理入门" target="_blank" rel="external">https://calm_java.gitee.io/blog/2020/02/17/labs-springboot2-database-version-control/</a></li><li class="post-copyright-license"><strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！</li></ul></blockquote><div class="panel panel-default panel-badger"><div class="panel-body"><figure class="media"><div class="media-left"><a href="https://github.com/yuyong725" target="_blank" class="img-burn thumb-sm visible-lg"><img src="/blog/images/avatar.jpg" class="img-rounded w-full" alt=""></a></div><div class="media-body"><h3 class="media-heading"><a href="https://github.com/yuyong725" target="_blank"><span class="text-dark">卡姆</span><small class="ml-1x">a Java coder need calm</small></a></h3><div>享受编程</div></div></figure></div></div></div></article><section id="comments"></section></div><nav class="bar bar-footer clearfix" data-stick-bottom><div class="bar-inner"><ul class="pager pull-left"><li class="prev"><a href="/blog/2020/02/18/labs-springboot2-job/" title="Spring Boot 定时任务入门"><i class="icon icon-angle-left" aria-hidden="true"></i><span>&nbsp;&nbsp;上一篇</span></a></li><li class="next"><a href="/blog/2020/02/17/labs-springboot2-sharding-datasource/" title="Spring Boot 分库分表入门"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a></li><li class="toggle-toc"><a class="toggle-btn" data-toggle="collapse" href="#collapseToc" aria-expanded="true" title="文章目录" role="button"><span>[&nbsp;</span><span>文章目录</span> <i class="text-collapsed icon icon-anchor"></i> <i class="text-in icon icon-close"></i> <span>]</span></a></li></ul><button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button><div class="bar-right"><div class="share-component" data-sites="wechat" data-mobile-sites="wechat"></div></div></div></nav><div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content donate"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><div class="modal-body"><div class="donate-box"><div class="donate-head"><p>感谢您的支持，我会继续努力的!</p></div><div class="tab-content"><div role="tabpanel" class="tab-pane fade active in" id="alipay"><div class="donate-payimg"><img src="/blog/images/donate/alipayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p></div><div role="tabpanel" class="tab-pane fade" id="wechatpay"><div class="donate-payimg"><img src="/blog/images/donate/wechatpayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p></div></div><div class="donate-footer"><ul class="nav nav-tabs nav-justified" role="tablist"><li role="presentation" class="active"><a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a></li><li role="presentation"><a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a></li></ul></div></div></div></div></div></div></main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter"></footer><script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script><script>window.jQuery||document.write('<script src="js/jquery.min.js"><\/script>')</script><script src="/blog/js/plugin.min.js"></script><script src="/blog/js/application.js"></script><script>!function(T){var N={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(未命名)"},ROOT_URL:"/blog/",CONTENT_URL:"/blog/content.json"};T.INSIGHT_CONFIG=N}(window)</script><script src="/blog/js/insight.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script src="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.js"></script><script src="//cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script><script type="text/javascript">var gitalk=new Gitalk({clientID:"adf0c8bfdd8796029917",clientSecret:"a88c2e2e83e2b180f62ad927dcb347871dfe9f4b",repo:"myblog-comment",owner:"yuyong725",admin:["yuyong725"],id:md5(location.pathname),distractionFreeMode:!0});gitalk.render("comments")</script></body></html><!-- rebuild by neat -->